home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.1 (Developer) [x86] / NeXT Step 3.1 Intel dev.cdr.dmg / NextDeveloper / Examples / AppKit / Draw / Group.m < prev    next >
Text File  |  1993-01-06  |  15KB  |  582 lines

  1. #import "draw.h"
  2.  
  3. /* Optimally viewed in a wide window.  Make your window big enough so that this comment fits entirely on one line w/o wrapping. */
  4.  
  5. #define GROUP_CACHE_THRESHOLD 4
  6.  
  7. @implementation Group : Graphic
  8. /*
  9.  * This Graphic is used to create heirarchical groups of other Graphics.
  10.  * It simply keeps a list of all the Graphics in the group and resizes
  11.  * and translates them as the Group object itself is resized and moved.
  12.  * It also passes messages sent to the Group onto its members.
  13.  *
  14.  * For efficiency, we cache the group whenever it passes the caching
  15.  * threshold.  Thus, grouping becomes a tool to let the user have some
  16.  * control over the memory/speed tradeoff (which can be different
  17.  * depending on the kind of drawing the user is making).
  18.  */
  19.  
  20. /* Factory method */
  21.  
  22. + initialize
  23. /*
  24.  * This bumps the class version so that we can compatibly read
  25.  * old Graphic objects out of an archive.
  26.  */
  27. {
  28.     [Group setVersion:3];
  29.     return self;
  30. }
  31.  
  32. /* Initialization */
  33.  
  34. - initList:(List *)list
  35. /*
  36.  * Creates a new grouping with list containing the list of Graphics
  37.  * in the group.  Groups of Groups is perfectly allowable.  We have
  38.  * to keep track of the largest linewidth in the group as well as
  39.  * whether any of the elements of the group have arrows since both
  40.  * of those attributes affect the extended bounds of the Group.
  41.  * We set any objects which might be cacheing (notably subgroups of
  42.  * this group) to be not cacheable since it is no use for them to
  43.  * cache themselves when we are caching them as well.  We also have
  44.  * to check to see if there are any TextGraphic's in the group
  45.  * because we can't cache ourselves if there are (unfortunately).
  46.  */
  47. {
  48.     int i;
  49.     NXRect r;
  50.     Graphic *graphic;
  51.  
  52.     [super init];
  53.  
  54.     gFlags.mightBeLinked = YES;
  55.     i = [list count];
  56.     graphic = [list objectAt:--i];
  57.     [graphic getBounds:&bounds];
  58.     gFlags.arrow = [graphic lineArrow];
  59.     linewidth = [graphic lineWidth];
  60.     bounds.size.width = MAX(1.0, bounds.size.width);
  61.     bounds.size.height = MAX(1.0, bounds.size.height);
  62.     while (i) {
  63.     graphic = [list objectAt:--i];
  64.     [graphic getBounds:&r];
  65.     [graphic setCacheable:NO];
  66.     r.size.width = MAX(1.0, r.size.width);
  67.     r.size.height = MAX(1.0, r.size.height);
  68.     NXUnionRect(&r, &bounds);
  69.     if (!gFlags.arrow && [graphic lineArrow]) gFlags.arrow = [graphic lineArrow];
  70.     if ([graphic lineWidth] > linewidth) linewidth = [graphic lineWidth];
  71.     if ([graphic isKindOf:[TextGraphic class]] || ([graphic isKindOf:[Group class]] && [(Group *)graphic hasTextGraphic])) hasTextGraphic = YES;
  72.     }
  73.  
  74.     components = list;
  75.     lastRect = bounds;
  76.  
  77.     return self;
  78. }
  79.  
  80. - free
  81. {
  82.     [components freeObjects];
  83.     [components free];
  84.     [cache free];
  85.     return [super free];
  86. }
  87.  
  88. /* Public methods */
  89.  
  90. - transferSubGraphicsTo:(List *)list at:(int)position
  91. /*
  92.  * Called by Ungroup.  This just unloads the components into the
  93.  * passed list, modifying the bounds of each of the Graphics
  94.  * accordingly (remember that when a Graphic joins a Group, its
  95.  * bounds are still kept in GraphicView coordinates (not
  96.  * Group-relative coordinates), but they may be non-integral,
  97.  * we can't allow non-integral bounds outside a group because
  98.  * it conflicts with the compositing rules (and we use
  99.  * compositing to move graphics around).
  100.  */
  101. {
  102.     int i, count;
  103.     Graphic *graphic;
  104.     NXRect gbounds;
  105.     BOOL zeroWidth, zeroHeight;
  106.  
  107.     count = [components count];
  108.     for (i = (count - 1); i >= 0; i--) {
  109.     graphic = [components objectAt:i];
  110.     [graphic getBounds:&gbounds];
  111.     if (!gbounds.size.width) {
  112.         zeroWidth = YES;
  113.         gbounds.size.width = 1.0;
  114.     } else zeroWidth = NO;
  115.     if (!gbounds.size.height) {
  116.         zeroHeight = YES;
  117.         gbounds.size.height = 1.0;
  118.     } else zeroHeight = NO;
  119.     NXIntegralRect(&gbounds);
  120.     if (zeroWidth) gbounds.size.width = 0.0;
  121.     if (zeroHeight) gbounds.size.height = 0.0;
  122.     [graphic setBounds:&gbounds];
  123.     [graphic setCacheable:YES];
  124.     [list insertObject:graphic at:position];
  125.     }
  126.  
  127.     return self;
  128. }
  129.  
  130. - (List *)subGraphics
  131. {
  132.     return components;
  133. }
  134.  
  135. /* Group must override all the setting routines to forward to components */
  136.  
  137. - makeGraphicsPerform:(SEL)aSelector with:(const void *)anArgument
  138. {
  139.     [components makeObjectsPerform:aSelector with:(id)anArgument];
  140.     [cache free];
  141.     cache = nil;
  142.     return self;
  143. }
  144.  
  145. - changeFont:sender
  146. {
  147.     return [self makeGraphicsPerform:@selector(changeFont:) with:sender];
  148. }
  149.  
  150. - (Font *)font
  151. {
  152.     int i;
  153.     Font *gfont, *font = nil;
  154.  
  155.     i = [components count];
  156.     while (i--) {
  157.     gfont = [[components objectAt:i] font];
  158.     if (gfont) {
  159.         if (font && font != gfont) {
  160.         font = nil;
  161.         break;
  162.         } else {
  163.         font = gfont;
  164.         }
  165.     }
  166.     }
  167.  
  168.     return font;
  169. }
  170.  
  171. - setLineWidth:(const float *)value
  172. {
  173.     return [self makeGraphicsPerform:@selector(setLineWidth:) with:value];
  174. }
  175.  
  176. - setGray:(const float *)value
  177. {
  178.     return [self makeGraphicsPerform:@selector(setGray:) with:value];
  179. }
  180.  
  181. - setFillColor:(NXColor *)aColor
  182. {
  183.     return [self makeGraphicsPerform:@selector(setFillColor:) with:aColor];
  184. }
  185.  
  186. - setFill:(int)mode
  187. {
  188.     return [self makeGraphicsPerform:@selector(setFill:) with:(void *)mode];
  189. }
  190.  
  191. - setLineColor:(NXColor *)aColor
  192. {
  193.     return [self makeGraphicsPerform:@selector(setLineColor:) with:aColor];
  194. }
  195.  
  196. - setLineCap:(int)value
  197. {
  198.     return [self makeGraphicsPerform:@selector(setLineCap:) with:(void *)value];
  199. }
  200.  
  201. - setLineArrow:(int)value
  202. {
  203.     return [self makeGraphicsPerform:@selector(setLineArrow:) with:(void *)value];
  204. }
  205.  
  206. - setLineJoin:(int)value
  207. {
  208.     return [self makeGraphicsPerform:@selector(setLineJoin:) with:(void *)value];
  209. }
  210.  
  211. /* Link methods */
  212.  
  213. /*
  214.  * Called after unarchiving and after a linkManager has been created for
  215.  * the document this Graphic is in.  Graphic's implementation of this just
  216.  * adds the link to the linkManager.
  217.  */
  218.  
  219. - reviveLink:(NXDataLinkManager *)linkManager
  220. {
  221.     [components makeObjectsPerform:@selector(reviveLink:) with:linkManager];
  222.     return self;
  223. }
  224.  
  225. /*
  226.  * This returns self if there is more than one linked Graphic in the Group.
  227.  * If aLink is not nil, returns the Graphic which is linked by that link.
  228.  * If aLink is nil, then it returns the one and only linked Graphic in the
  229.  * group or nil otherwise.  Used when updating the link panel and when
  230.  * redrawing link outlines.
  231.  */
  232.  
  233. - (Graphic *)graphicLinkedBy:(NXDataLink *)aLink
  234. {
  235.     int i, linkCount = 0;
  236.     Graphic *graphic = nil;
  237.  
  238.     for (i = [components count]-1; i >= 0; i--) {
  239.     if (graphic = [[components objectAt:i] graphicLinkedBy:aLink]) {
  240.         if ([graphic isKindOf:[Group class]]) return graphic;
  241.         linkCount++;
  242.     }
  243.     }
  244.  
  245.     return (linkCount <= 1) ? graphic : self;
  246. }
  247.  
  248. /*
  249.  * When you copy/paste a Graphic, its identifier must be reset to something
  250.  * different since you don't want the pasted one to have the same identifier
  251.  * as the copied one!  See gvPasteboard.m.
  252.  */
  253.  
  254. - resetIdentifier
  255. {
  256.     [components makeObjectsPerform:@selector(resetIdentifier)];
  257.     return self;
  258. }
  259.  
  260. /*
  261.  * Used when creating an NXSelection representing all the Graphics
  262.  * in a selection.  Has to recurse through Groups because you still
  263.  * want the NXSelection to be valid even if the Graphics are ungrouped
  264.  * in the interim between the time the selection is determined to the
  265.  * time the links stuff asks questions about the selection later.
  266.  */
  267.  
  268. - writeIdentifierTo:(char *)buffer
  269. {
  270.     int i = [components count];
  271.     char *s = buffer;
  272.  
  273.     if (i) {
  274.     [[components objectAt:--i] writeIdentifierTo:s];
  275.     s += strlen(s);
  276.     while (i--) {
  277.         *s++ = ' ';
  278.         [[components objectAt:i] writeIdentifierTo:s];
  279.         s += strlen(s);
  280.     }
  281.     }
  282.  
  283.     return self;
  284. }
  285.  
  286. /*
  287.  * This is used by the links stuff to allocate a buffer big enough to
  288.  * put all the identifiers for all the graphics in this Group into.
  289.  */
  290.  
  291. - (int)graphicCount
  292. {
  293.     int count = 0, i = [components count];
  294.     while (i--) count += [[components objectAt:i] graphicCount];
  295.     return count;
  296. }
  297.  
  298. /*
  299.  * See the method findGraphicInSelection: in gvLinks.m to see how this
  300.  * method is used (it basically just lets you get back to a Graphic
  301.  * from its identifier whether its in a Group or not).
  302.  */
  303.  
  304. - (Graphic *)graphicIdentifiedBy:(int)anIdentifier
  305. {
  306.     int i = [components count];
  307.     while (i--) {
  308.     Graphic *graphic = [components objectAt:i];
  309.     if (graphic = [graphic graphicIdentifiedBy:anIdentifier]) return graphic;
  310.     }
  311.     return nil;
  312. }
  313.  
  314. /*
  315.  * We pass this method onto all the things inside the group since
  316.  * there might be linked things inside the group.
  317.  */
  318.  
  319. - readLinkFromPasteboard:(Pasteboard *)pboard usingManager:(NXDataLinkManager *)linkManager useNewIdentifier:(BOOL)useNewIdentifier
  320. {
  321.     int i = [components count];
  322.     while (i--) {
  323.     Graphic *graphic = [components objectAt:i];
  324.     if ([graphic mightBeLinked]) [graphic readLinkFromPasteboard:pboard usingManager:linkManager useNewIdentifier:useNewIdentifier];
  325.     }
  326.     return nil;
  327. }
  328.  
  329. /* Form Entry methods.  See TextGraphic.m for details. */
  330.  
  331. - (BOOL)hasFormEntries
  332. {
  333.     int i = [components count];
  334.     while (i--) if ([[components objectAt:i] hasFormEntries]) return YES;
  335.     return NO;
  336. }
  337.  
  338. - (BOOL)writeFormEntryToStream:(NXStream *)stream
  339. {
  340.     BOOL retval = NO;
  341.     int i = [components count];
  342.     while (i--) if ([[components objectAt:i] writeFormEntryToStream:stream]) retval = YES;
  343.     return retval;
  344. }
  345.  
  346. /* Notification methods */
  347.  
  348. - wasRemovedFrom:(GraphicView *)sender
  349. {
  350.     [components makeObjectsPerform:@selector(wasRemovedFrom:) with:sender];
  351.     [cache free];
  352.     cache = nil;
  353.     return self;
  354. }
  355.  
  356. - wasAddedTo:(GraphicView *)sender
  357. {
  358.     [components makeObjectsPerform:@selector(wasAddedTo:) with:sender];
  359.     return self;
  360. }
  361.  
  362. /* Color drag-and-drop support. */
  363.  
  364. - (Graphic *)colorAcceptorAt:(const NXPoint *)point
  365. {
  366.     int i, count;
  367.     Graphic *graphic;
  368.  
  369.     count = [components count];
  370.     for (i = 0; i < count; i++) {
  371.     if (graphic = [[components objectAt:i] colorAcceptorAt:point]) return graphic;
  372.     }
  373.  
  374.     return nil;
  375. }
  376.  
  377. /* We can't cache ourselves if we have a TextGraphic in the Group. */
  378.  
  379. - (BOOL)hasTextGraphic
  380. {
  381.     return hasTextGraphic;
  382. }
  383.  
  384. - setCacheable:(BOOL)flag
  385. /*
  386.  * Sets whether we do caching of this Group or not.
  387.  */
  388. {
  389.     dontCache = flag ? NO : YES;
  390.     if (dontCache) {
  391.     [cache free];
  392.     cache = nil;
  393.     }
  394.     return self;
  395. }
  396.  
  397. - (BOOL)isCacheable
  398. {
  399.     return !hasTextGraphic && !dontCache;
  400. }
  401.  
  402. - draw
  403. /*
  404.  * Individually scales and translates each Graphic in the group and draws
  405.  * them.  This is done this way so that ungrouping is trivial.  Note that
  406.  * if we are caching, we need to take the extra step of translating
  407.  * everything to the origin, drawing them in the cache, then translating
  408.  * them back.
  409.  */
  410. {
  411.     int i;
  412.     Graphic *g;
  413.     NXRect eb, b;
  414.     float sx = 1.0, sy = 1.0, tx, ty;
  415.     BOOL changed, changedSize, caching = NO;
  416.  
  417.     if (bounds.size.width < 1.0 || bounds.size.height < 1.0 || !components) return self;
  418.  
  419.     changedSize = lastRect.size.width != bounds.size.width || lastRect.size.height != bounds.size.height;
  420.     changed = changedSize || lastRect.origin.x != bounds.origin.x || lastRect.origin.y != bounds.origin.y;
  421.  
  422.     if ((changedSize || !cache) && NXDrawingStatus == NX_DRAWING) {
  423.     [cache free];
  424.     cache = nil;
  425.     if (DrawStatus != Resizing && [self isCacheable] && [components count] > GROUP_CACHE_THRESHOLD) {
  426.         caching = YES;
  427.         [self getExtendedBounds:&eb];
  428.         cache = [[NXImage allocFromZone:[self zone]] initSize:&eb.size];
  429.         [cache lockFocus];
  430.         [[[NXApp focusView] window] reenableDisplay];    /* workaround for AppKit bug? */
  431.         PStranslate(- eb.origin.x, - eb.origin.y);
  432.         PSsetalpha(0.0);
  433.         PSsetgray(NX_WHITE);
  434.         NXRectFill(&eb);
  435.         PSsetalpha(1.0);
  436.     }
  437.     }
  438.  
  439.     if (changedSize) {
  440.     sx = bounds.size.width / lastRect.size.width;
  441.     sy = bounds.size.height / lastRect.size.height;
  442.     }
  443.  
  444.     i = [components count];
  445.     while (i) {
  446.     g = [components objectAt:--i];
  447.     if (changed) {
  448.         [g getBounds:&b];
  449.         tx = (bounds.origin.x + ((b.origin.x - lastRect.origin.x) / lastRect.size.width * bounds.size.width)) - b.origin.x;
  450.         ty = (bounds.origin.y + ((b.origin.y - lastRect.origin.y) / lastRect.size.height * bounds.size.height)) - b.origin.y;
  451.         b.origin.x = b.origin.x + tx;
  452.         b.origin.y = b.origin.y + ty;
  453.         b.size.width = b.size.width * sx;
  454.         b.size.height = b.size.height * sy;
  455.         [g setBounds:&b];
  456.     }
  457.     if (NXDrawingStatus != NX_DRAWING || !cache || caching) {
  458.         [g setGraphicsState];    /* does a gsave ... */
  459.         [g draw];
  460.         PSgrestore();        /* ... so we need this grestore */
  461.     }
  462.     }
  463.  
  464.     if (cache && NXDrawingStatus == NX_DRAWING) {
  465.     if (caching) {
  466.         [cache unlockFocus];
  467.     } else {
  468.         [self getExtendedBounds:&eb];
  469.     }
  470.     [cache composite:NX_SOVER toPoint:&eb.origin];
  471.     }
  472.  
  473.     lastRect = bounds;
  474.  
  475.     return self;
  476. }
  477.  
  478. - (BOOL)hit:(const NXPoint *)point
  479. /*
  480.  * Gets a hit if any of the items in the group gets a hit.
  481.  */
  482. {
  483.     int i;
  484.     NXPoint p;
  485.     float px, py;
  486.     Graphic *graphic;
  487.  
  488.     if ([super hit:point]) {
  489.     if (components) {
  490.         p = *point;
  491.         px = (p.x - bounds.origin.x) / bounds.size.width;
  492.         p.x = px * lastRect.size.width + lastRect.origin.x;
  493.         py = (p.y - bounds.origin.y) / bounds.size.height;
  494.         p.y = py * lastRect.size.height + lastRect.origin.y;
  495.         i = [components count];
  496.         while (i) {
  497.         graphic = [components objectAt:--i];
  498.         if ([graphic hit:&p]) return YES;
  499.         }
  500.     } else {
  501.         return YES;
  502.     }
  503.     }
  504.  
  505.     return NO;
  506. }
  507.  
  508. /* Compatibility methods */
  509.  
  510. - replaceWithImage
  511. /*
  512.  * Since we got rid of Tiff and PSGraphic and replaced them
  513.  * with the unified Image graphic, we need to go through our
  514.  * list and replace all of them with an Image graphic.
  515.  */
  516. {
  517.     int i;
  518.     Graphic *graphic, *newGraphic;
  519.  
  520.     for (i = [components count]-1; i >= 0; i--) {
  521.     graphic = [components objectAt:i];
  522.     newGraphic = [graphic replaceWithImage];
  523.     if (graphic != newGraphic) {
  524.         if (graphic) {
  525.         [components replaceObjectAt:i with:newGraphic];
  526.         } else {
  527.         [components removeObjectAt:i];
  528.         }
  529.     }
  530.     }
  531.  
  532.     return self;
  533. }
  534.  
  535. /* Archiving methods */
  536.  
  537. - write:(NXTypedStream *)stream
  538. /*
  539.  * Just writes out the components.
  540.  */
  541. {
  542.     [super write:stream];
  543.     NXWriteTypes(stream, "@", &components);
  544.     NXWriteType(stream, "c", &dontCache);
  545.     NXWriteRect(stream, &lastRect);
  546.     NXWriteType(stream, "c", &hasTextGraphic);
  547.     return self;
  548. }
  549.  
  550. static BOOL checkForTextGraphic(List *list)
  551. {
  552.     int i;
  553.     Graphic *graphic;
  554.  
  555.     for (i = [list count]-1; i >= 0; i--) {
  556.     graphic = [list objectAt:i];
  557.     if ([graphic isKindOf:[TextGraphic class]] || ([graphic isKindOf:[Group class]] && [(Group *)graphic hasTextGraphic])) return YES;
  558.     }
  559.  
  560.     return NO;
  561. }
  562.  
  563. - read:(NXTypedStream *)stream
  564. {
  565.     [super read:stream];
  566.     NXReadTypes(stream, "@", &components);
  567.     lastRect = bounds;
  568.     if (NXTypedStreamClassVersion(stream, "Group") > 1) {
  569.     NXReadType(stream, "c", &dontCache);
  570.     NXReadRect(stream, &lastRect);
  571.     }
  572.     if (NXTypedStreamClassVersion(stream, "Group") > 2) {
  573.     NXReadType(stream, "c", &hasTextGraphic);
  574.     } else {
  575.     hasTextGraphic = checkForTextGraphic(components);
  576.     }
  577.     return self;
  578. }
  579.  
  580. @end
  581.  
  582.